﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace FadeLang.VirtualMachine
{
    public class VirtualMachine
    {
        private readonly List<dynamic> dataStack = new List<dynamic>();

        private readonly Dictionary<OpCode, Action> dispatchTable;

        private int instructionPointer;
        private int lastinstructionPointer = -100;
        private int opcodeListLength;

        public VirtualMachine() {
            dispatchTable = new Dictionary<OpCode, Action> {
                [OpCode.Add] = () => { Push(Pop() + Pop()); },
                [OpCode.Sub] = () => {
                    var last = Pop();
                    Push(Pop() - last);
                },
                [OpCode.Mul] = () => { Push(Pop() * Pop()); },
                [OpCode.Div] = () => {
                    var last = Pop();
                    Push(Pop() / last);
                },
                [OpCode.Jmp] = () => { instructionPointer = Pop(); },
                [OpCode.Print] = () => { Console.Write(Pop()); },
                [OpCode.If] = If,
                [OpCode.ReadLine] = () => { Push(Console.ReadLine()); },
                [OpCode.Call] = Call,
                [OpCode.Return] = Return,
                [OpCode.Exit] = () => { instructionPointer = opcodeListLength; }
            };
        }

        private void Return() {
            instructionPointer = lastinstructionPointer;
        }

        private void Call() {
            lastinstructionPointer = instructionPointer;
            instructionPointer = Pop();
        }

        private void If() {
            var falseClause = Pop();
            var trueClause = Pop();
            var condition = Pop();
            Push(condition ? trueClause : falseClause);
        }

        public void Run(List<dynamic> opcodes) {
            Optimizer.Optimize(opcodes);
            Run(opcodes.ToArray());
        }

        private void Run(params dynamic[] program) {
            opcodeListLength = program.Length;
            while (0 <= instructionPointer && instructionPointer < opcodeListLength) {
                var opcode = program[instructionPointer++];
                if (opcode is OpCode) {
                    dispatchTable[opcode]();
                }
                else {
                    Push(opcode);
                }
            }
        }

        private dynamic Pop() {
            var last = dataStack.Last();
            dataStack.RemoveAt(dataStack.Count - 1);
            return last;
        }

        private void Push(dynamic value) {
            dataStack.Add(value);
        }
    }
}